package com.example.redis_redisson_demo.web.redisson;

import com.example.redis_redisson_demo.common.utils.R;
import com.example.redis_redisson_demo.dao.AttrDao;
import com.example.redis_redisson_demo.entity.AttrEntity;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 介绍redisson提供的分布式对象
 */
@RestController
@RequestMapping("/redissondistributedobject")
public class RedissonDistributedObjectController {
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    AttrDao attrDao;
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    /**
     * 通用格式。
     * 每个Redisson对象实例都会有一个与之对应的Redis数据实例，可以通过 RedissonClient.getxxx的方式来创建这个实例
     * 在文档 https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/RedissonClient.html
     * 中可以查看，之后也会详细介绍。
     *
     * @return
     */
    @RequestMapping("/util")
    public R redissonUtil() {
        String redissonPrefix = "demo:redisson:disObj:";
        // 例如这里就获取了一个Map数据类型。
        // Map属于是分布式集合，将在之后进行讲解。
        RMap<String, String> map = redissonClient.getMap(redissonPrefix + "map");
        map.put("name", "wangwu");
        return R.ok();
    }

    /**
     * 所有与Redis key相关的操作都归纳在RKeys这个接口里：
     * 可以理解为 StringRedisTemplate 的第一层操作内容
     * https://www.javadoc.io/doc/org.redisson/redisson/3.10.0/org/redisson/api/RKeys.html
     *
     * @return
     */
    @RequestMapping("/keys")
    public R redissonKeys() {
        String redissonPrefix = "demo:redisson:disObj:";
        RKeys redissonClientKeys = redissonClient.getKeys();

        /**
         * boolean move(String name, int database)
         * 将一个k-v移动到redis中的另一个库中
         * redis原生默认划分为16个库，编号从0开始递增。
         *
         * redissonClientKeys.move(redissonPrefix + "map", 2);
         *
         * 提供异步模式下的move
         * redissonClientKeys.moveAsync(redissonPrefix + "map", 2);
         */


        /**
         * void migrate(String name, String host, int port, int database, long timeout)
         * 将一个k-v从一个redis中移动到另一个redis的指定编号库中，并指定了超时时间
         *
         * redissonClientKeys.migrate(redissonPrefix + "map", "127.0.0.1", 6999, 2, 3000);
         *
         * 提供异步模式下的migrate
         * redissonClientKeys.migrateAsync(redissonPrefix + "map", "127.0.0.1", 6999, 2, 3000);
         */


        /**
         * void copy(String name, String host, int port, int database, long timeout)
         * 复制。
         * 同样提供异步模式下的 copyAsync
         */

        /**
         * boolean expire(String name, long timeToLive, TimeUnit timeUnit)
         * 为一个k-v设置过期时间
         * 同样提供异步模式下的 expireAsync
         */
        redissonClientKeys.expire(redissonPrefix + "map", 3, TimeUnit.SECONDS);
        RFuture<Boolean> expireAsync = redissonClientKeys.expireAsync(redissonPrefix + "map", 3, TimeUnit.SECONDS);

        /**
         * boolean expireAt(String name, long timestamp)
         * 直接指定一个时间戳，声明k-v将在此时过期
         * 提供 expireAtAsync
         */
        redissonClientKeys.expireAt(redissonPrefix + "map", new Date().getTime());

        /**
         * boolean clearExpire(String name)
         * 清除该k-v的过期时间
         * 提供 clearExpireAsync
         */
        redissonClientKeys.clearExpire(redissonPrefix + "map");

        /**
         * boolean renamenx(String oldName, String newName)
         * 将 oldName的k-v更改为newName，前提是不存在newName的k-v。成功改名返回true，失败返回false
         * 提供 renamenxAsync
         */
        redissonClientKeys.renamenx(redissonPrefix + "map", redissonPrefix + "newmap");

        /**
         * void rename(String currentName, String newName)
         * 无视 newName是否存在。如果存在则该命令无效
         * 提供 renameAsync
         */
        redissonClientKeys.rename(redissonPrefix + "map", redissonPrefix + "newmap");

        /**
         * long remainTimeToLive(String name)
         * 返回k-v的剩余存活时间
         * 如果不存在该key返回-2，如果该key不存在过期时间返回-1
         * 提供 remainTimeToLiveAsync
         */
        long remainTimeToLive = redissonClientKeys.remainTimeToLive(redissonPrefix + "map");

        /**
         * long touch(String... names)
         * 访问给定的key，用以更新他们的 最后访问时间
         * 返回成功访问的数量
         * 提供 touchAsync
         */
        long touch = redissonClientKeys.touch(redissonPrefix + "map", redissonPrefix + "map");

        /**
         * long countExists(String... names)
         * 检查给定的key是否存在，返回存在的个数
         */
        long countExists = redissonClientKeys.countExists(redissonPrefix + "map", redissonPrefix + "map");

        /**
         * RType getType(String key)
         * 获取指定key的数据类型
         * 提供 typeAsync
         */
        RType type = redissonClientKeys.getType(redissonPrefix + "map");

        /**
         * int getSlot(String key)
         * 获取指定k-v所在的槽位。只有当redis以集群方式部署时才有效
         * 提供 getSlotAsync
         */
        int clientKeysSlot = redissonClientKeys.getSlot(redissonPrefix + "map");

        /**
         * Iterable<String> getKeysByPattern(String pattern)
         * 按照正则表达式寻找key，以可迭代形式返回.
         * 由于Redis会使用scan来完成该指令，为防止全盘scan拖慢系统，
         * 该指令会向redis发送多个请求，每个请求只要求Scan扫描到10个结果即可返回。多个请求结果拼接拼接后返回给调用者
         * 提供 getKeysByPatternAsync。建议减少该指令的使用，或尽量使用异步模式
         */
        Iterable<String> keysByPattern = redissonClientKeys.getKeysByPattern(redissonPrefix + "*");

        /**
         * Iterable<String> getKeysByPattern(String pattern, int count)
         * 上一个方法每个Scan只扫到10个即返回，这里是扫到count个
         */
        Iterable<String> keysByPatternNum = redissonClientKeys.getKeysByPattern(redissonPrefix + "*", 20);

        /**
         * Iterable<String> getKeys()
         * 返回所有的key，和getKeysByPattern一样的逻辑。
         * 同样有 getKeys(int count)
         * 提供 getKeysAsync
         */

        /**
         * Stream<String> getKeysStreamByPattern(String pattern) / Stream<String> getKeysStream()
         * 和上面两个完全相同的逻辑，只不过返回的是Stream
         */

        /**
         * long deleteByPattern(String pattern)
         * long delete(RObject... objects)
         * long delete(String... keys)
         * 删除。注意，第一个删除操作为非原子操作，后两个均为原子操作
         */

        return R.ok();
    }

    /**
     * 通用对象桶，一种通用对象桶可以用来存放任类型的对象
     * 桶的名称就是k-v的key名称，向桶中放的数据就是v
     * <p>
     * 对象桶的作用是 可以无视类型转换，向桶中放置任何类型的对象。
     * https://www.javadoc.io/doc/org.redisson/redisson/3.10.0/org/redisson/api/RBucket.html
     *
     * @return
     */
    @RequestMapping("/bucket")
    public R redissonBucket() {
        String redissonPrefix = "demo:redisson:disObj:";
        AttrEntity attrEntity = attrDao.selectById(2L);

        /**
         * RBucket<Object> getBucket(key);
         * 获取一个对象桶。桶的名称就是 key
         * 如果redis中已经存在这个桶则获得这个桶的映射，否则之后的操作会向redis中放入这个桶
         */
        RBucket<Object> bucket = redissonClient.getBucket(redissonPrefix + "bucket");

        /**
         * void set(V value) / void set(V value, long timeToLive, TimeUnit timeUnit)
         * 向对象桶中放入任意类型的对象
         * 提供异步实现 setAsync
         *
         * 注，任何类型的对象都可以往桶里放，且无需考虑类型转换。新放进去的对象将覆盖旧的对象(旧对象的过期设置也会被丢弃)
         */
        bucket.set("hello");
        bucket.set(attrEntity, 60, TimeUnit.SECONDS);
        List<Integer> temp = new ArrayList<>();
        temp.add(1);
        temp.add(62);
        bucket.set(temp);

        /**
         * boolean trySet(V value) / boolean trySet(V value, long timeToLive, TimeUnit timeUnit)
         * 尝试向一个空对象桶中设置对象，返回成功失败标志. 目标桶必须是空桶，否则一定失败
         * 提供异步实现 trySetAsync
         */
        boolean trySet = bucket.trySet(9);

        /**
         * long size()
         * 将对象的大小的字节长度返回
         * 提供异步实现 sizeAsync
         */
        long size = bucket.size();

        /**
         * V get() / V getAndDelete() / V getAndSet(V newValue)
         * 获取对象桶中的对象，并在获取成功后执行其他的操作
         * 均提供异步实现
         */
        bucket.getAndSet("newValue", 300, TimeUnit.SECONDS);

        /**
         * boolean compareAndSet(Object v1, Object v2)
         * 使用CAS进行更新。返回是否更新成功
         * 方法自动调用equal比较源value和v1，v2不必和v1保持相同数据类型
         * 提供异步实现
         */
        boolean compareAndSet = bucket.compareAndSet("newValue", "CASValue");

        /**
         * bucket继承 RKeys中的部分方法，例如 copy / expire等等
         * 具体继承了哪些方法去看官方文档，不再赘述
         */

        return R.ok();
    }

    /**
     * 原子长整型
     * 在迸发环境下保证线程安全的原子长整型类，提供同步 异步的标准接口
     * https://www.javadoc.io/doc/org.redisson/redisson/3.10.0/org/redisson/api/RAtomicLong.html
     *
     * @return
     */
    @RequestMapping("/atomiclong")
    public R redissonAtomicLong() {
        String redissonPrefix = "demo:redisson:disObj:";
        /**
         * RAtomicLong getAtomicLong(K key);
         * 获取一个原子长整型对象。如果redis中存在则返回其映射，否则之后的操作会在redis中创建key命名的原子长整型
         */
        RAtomicLong atomicLong = redissonClient.getAtomicLong(redissonPrefix + "atomiclong");

        /**
         * void set(long newValue)
         * 设置一个新值。不提供同时设置过期时间的实现方法
         * 提供异步实现 setAsync
         */
        atomicLong.set(33L);

        /**
         * long get()
         * 返回值
         * 提供异步实现
         */
        long get = atomicLong.get();

        /**
         * long getAndDecrement() / long decrementAndGet()
         * 返回当前值后，原子性减一 / 先原子性减一后返回更新后的值
         * 均提供异步实现
         */
        long andDecrement = atomicLong.getAndDecrement();

        /**
         * long incrementAndGet() / long getAndIncrement()
         * 返回当前值后，原子性加一 / 先原子性加一后返回更新后的值
         * 均提供异步实现
         */
        long andIncrement = atomicLong.getAndIncrement();

        /**
         * long addAndGet(long delta)
         * 原子性加delta后，返回更新后的值.delta可以为负数，实现相减
         * 提供异步实现
         */
        long addAndGet = atomicLong.addAndGet(7L);

        /**
         * boolean compareAndSet(long expect, long update)
         * 使用CAS且原子性重新赋值
         * 提供异步实现
         */

        /**
         * long getAndDelete() / long getAndAdd(long delta) / long getAndSet(long newValue)
         * 返回当前值后，原子性进行其他操作
         * 均提供异步实现
         */

        /**
         * 继承RKeys中的部分方法，例如 copy / expire等等
         * 具体继承了哪些方法去看官方文档，不再赘述
         */

        return R.ok();
    }

    /**
     * 原子双精度 AtomicDouble
     * 和原子长整型完全相同，不再赘述
     * https://www.javadoc.io/doc/org.redisson/redisson/3.10.0/org/redisson/api/RAtomicDouble.html
     */

    /**
     * Redisson提供操作redis话题的接口。没有学习redis话题的内容，因为大多数情况下都是用rabbitmq了
     */

    /**
     * 布隆过滤器。
     * 一种比哈希表更高效的一种用于存放大量随机数据，并以O(1)快速查找目标的算法
     * 缺点在与布隆过滤器存在误算率，随着存入元素数量增加，误算率也随之增加。
     * 常见的补救办法是建立一个小的白名单，存储那些可能被误判的元素。但是如果元素数量太少，则使用散列表足矣。
     *
     * @return
     */
    @RequestMapping("/bloomfilter")
    public R redissonBloomFilter() {
        String redissonPrefix = "demo:redisson:disObj:";
        AttrEntity attrEntity = attrDao.selectById(2L);

        /**
         * 获得一个布隆过滤器
         */
        RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(redissonPrefix + "bloomfilter");

        /**
         * boolean tryInit(long expectedInsertions, double falseProbability)
         * 初始化布隆过滤器，预计统计元素数量为expectedInsertions，期望误差率为falseProbability
         * 不提供异步实现方法
         */
        bloomFilter.tryInit(55000000L, 0.03);

        /**
         * boolean add(T object)
         * 向其中添加任意类型的对象。可以天剑不同类型的对象
         * 不提供异步实现方法
         */
        bloomFilter.add(attrEntity);
        bloomFilter.add("newValue");

        /**
         * boolean contains(T object)
         * 检查布隆过滤器中是否存在某个对象。需要完整提供目标对象，方法使用equals进行比较
         * 不提供异步实现方法
         */
        bloomFilter.contains("newValue");

        /**
         * 删除这个布隆过滤器。
         * 提供异步方法
         */
        bloomFilter.delete();
        return R.ok();
    }

    /**
     * 整长型累加器（LongAdder）/ 双精度浮点累加器（DoubleAdder）
     * 基于Redis的Redisson分布式整长型累加器（LongAdder）采用了与java.util.concurrent.atomic.LongAdder类似的接口。
     * 通过利用客户端内置的LongAdder对象，为分布式环境下递增和递减操作提供了很高得性能。
     * 据统计其性能最高比分布式AtomicLong对象快 12000 倍。完美适用于分布式统计计量场景。
     *
     * @return
     */
    @RequestMapping("/longadder")
    public R redissonLongAdder() {
        String redissonPrefix = "demo:redisson:disObj:";

        /**
         * 获取一个LongAdder
         */
        RLongAdder longAdder = redissonClient.getLongAdder(redissonPrefix + "longadder");

        /**
         * 不是很懂这个。貌似会在redis上同时维护longAdder和分布式环境下的所有LongAdder实例
         */
        longAdder.add(12);
        longAdder.increment();
        longAdder.sum();

        /**
         * 这种在分布式集群上维护的锁(或者类似锁的东西)，注意释放的逻辑:
         *   要么是当前RedissonClient断开链接，此时这个锁依然在redis上存在
         *   要么是获取到这个锁，用destroy销毁，此时redis上锁也被删除。
         */
        longAdder.destroy();
        longAdder.delete();
        return R.ok();
    }

    /**
     * 基于Redis的分布式限流器（RateLimiter）可以用来在分布式环境下现在请求方的调用频率。
     * 既适用于不同Redisson实例下的多线程限流，也适用于相同Redisson实例下的多线程限流。
     * 该算法不保证公平性。除了同步接口外，还提供了异步（Async）、反射式（Reactive）和RxJava2标准的接口。
     * <p>
     * 和信号量锁的区别在于该对象可以自动在指定的时间间隔内产生令牌
     *
     * @return
     */
    @RequestMapping("/ratelimiter")
    public R redissonRateLimiter() {
        String redissonPrefix = "demo:redisson:disObj:";

        /**
         * 获取一个限流器。
         * 如果redis中已存在则返回其映射，否则接下的操作将在redis中创建该限流器
         */
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(redissonPrefix + "ratelimiter");

        /**
         * boolean trySetRate(RateType mode, long rate, long rateInterval, RateIntervalUnit rateIntervalUnit)
         * 为一个限流器设置时间参数
         * 第一个输入参数有两个可选值:
         *  OVERALL: 对所有RedissonClient上使用同名的限流器都起作用
         *  PER_CLIENT: 只对当前RedissonClient上使用同名的限流器起作用
         *  注意前提都是使用同名限流器，不然肯定没作用
         *
         * 该设置只对未初始化的限流器起效。如果redis中已经存在同名的限流器且经过了初始化，则再次设置无效
         */
        rateLimiter.trySetRate(RateType.OVERALL, 2, 15, RateIntervalUnit.SECONDS);  // 每1分钟产生2个令牌

        /**
         * 获得限流器的设置。
         */
        RateLimiterConfig config = rateLimiter.getConfig();

        /**
         * void acquire() / void acquire(long permits)。 阻塞获取直到成功获取1个 / permits 个令牌
         * boolean tryAcquire() / boolean tryAcquire(long permits)。 尝试获取1个 / permits 个令牌，如果失败立即返回
         * boolean tryAcquire(long timeout, TimeUnit unit) / boolean tryAcquire(long permits, long timeout, TimeUnit unit)
         *   尝试获取1个 / permits个令牌，如果失败，阻塞指定时间后返回
         */
        boolean try1 = rateLimiter.tryAcquire(2, 2, TimeUnit.SECONDS);
        boolean try2 = rateLimiter.tryAcquire(1, 2, TimeUnit.SECONDS);
        if(!try2){
            System.out.println("已被限流");
        }

        /**
         * 实质是在redis中维护一个2-15的RRateLimit对象。
         * 当使用tryAcquire时，会产生一个value为2过期时间为15秒的信号量。
         * 这样当有其他tryAcquire时，如果信号量存在则由信号量的值决定，如果不存在则先根据RRateLimit创建信号量，然后再由信号量的值决定。
         * 参照下面的a() b() 两个方法理解。
         */

        /**
         * 因为限流器不是最小粒度的redis对象，所以无法直接给限流器对象设置过期时间
         * 但是就像上面讲的，实际上是在redis中维护一个 key名称的普通对象，可以给这个普通对象添加过期时间
         */

        return R.ok();
    }


    @RequestMapping("/1")
    public R a(){
        String redissonPrefix = "demo:redisson:disObj:";
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(redissonPrefix + "ratelimiter");
        // 只能用这种方法设置过期时间
        stringRedisTemplate.expire(redissonPrefix + "ratelimiter", 200, TimeUnit.SECONDS);
        rateLimiter.trySetRate(RateType.OVERALL, 2, 15, RateIntervalUnit.SECONDS);  // 每1分钟产生2个令牌
        return R.ok();
    }

    @RequestMapping("/2")
    public R b(){
        String redissonPrefix = "demo:redisson:disObj:";
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(redissonPrefix + "ratelimiter");
        boolean b = rateLimiter.tryAcquire(1);
        if(!b){
            System.out.println("已被阻塞");
        }
        return R.ok();
    }


}
